import 'package:flutter/material.dart';

/*
  https://flutter.cn/docs/development/ui/widgets-intro

*/

class Product {
  final String name;

  const Product({required this.name});
}

typedef CartChangedCallback = Function(Product product, bool inCart);

class ShoppingListItem extends StatelessWidget {
  final Product product;
  final bool inCart;
  final CartChangedCallback onCartChanged;

  ShoppingListItem(
      {required this.product,
      required this.inCart,
      required this.onCartChanged})
      : super(key: ObjectKey(product));

  Color _getColor(BuildContext context) {
    return inCart ? Colors.black54 : Theme.of(context).primaryColor;
  }

  TextStyle? _getTextStyle(BuildContext context) {
    if (!inCart) return null;

    return const TextStyle(
        color: Colors.black54, decoration: TextDecoration.lineThrough);
  }

  @override
  Widget build(BuildContext context) {
    return ListTile(
      onTap: () {
        onCartChanged(product, inCart);
      },
      leading: CircleAvatar(
        backgroundColor: _getColor(context),
        child: Text(product.name[0]),
      ),
      title: Text(
        product.name,
        style: _getTextStyle(context),
      ),
    );
  }
}

class ShoppingList extends StatefulWidget {
  final List<Product> products;

  const ShoppingList({required this.products, super.key});

  @override
  State<ShoppingList> createState() => _ShoppingListState();
}

class _ShoppingListState extends State<ShoppingList> {
  final _shoppingCart = <Product>{};

  void _handleCartChanged(Product product, bool inCart) {
    setState(() {
      if (!inCart) {
        _shoppingCart.add(product);
      } else {
        _shoppingCart.remove(product);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Shopping List'),
      ),
      body: ListView(
        padding: const EdgeInsets.symmetric(vertical: 8.0),
        children: widget.products.map((product) {
          return ShoppingListItem(
            product: product,
            inCart: _shoppingCart.contains(product),
            onCartChanged: _handleCartChanged,
          );
        }).toList(),
      ),
    );
  }
}

class ShopStatefulWidgetsApp2 extends StatelessWidget {
  const ShopStatefulWidgetsApp2({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'shopping app',
      home: ShoppingList(
        products: [
          Product(name: "Eggs"),
          Product(name: 'Flour'),
          Product(name: 'Chocolate chips')
        ],
      ),
    );
  }
}

/*

  ShoppingList 类继承自 StatefulWidget，这意味着这个 widget 存储着可变状态。当 ShoppingList 首次插入到 widget 树中时，框架调用 createState() 函数来创建 _ShoppingListState 的新实例，
  以与树中的该位置相关联。（注意，State 的子类通常以下划线开头进行命名，表示它们的实现细节是私有的）当该 widget 的父 widget 重建时，父 widget 首先会创建一个 ShoppingList 的实例，但是框架会复用之前创建的 _ShoppingListState，而不会重新调用 createState。

为了访问当前 ShoppingList 的属性， _ShoppingListState 可以使用它的 widget 属性。当父组件重建一个新的 ShoppingList 时， _ShoppingListState 会使用新的 widget 值来创建。
如果希望在 widget 属性更改时收到通知，则可以重写 didUpdateWidget() 函数，该函数将 oldWidget 作为参数传递，以便将 oldWidget 与当前 widget 进行比较。

当处理 onCartChanged 回调时，_ShoppingListState 通过增加或删除 _shoppingCart 中的产品来改变其内部状态。为了通知框架它改变了它的内部状态，需要调用 setState()，将该 widget 标记为「dirty」（脏标记），
并且计划在下次应用需要更新屏幕时重新构建它。如果在修改 widget 的内部状态后忘记调用 setState，框架将不知道这个 widget 是「dirty」（脏标记），并且可能不会调用 widget 的 build() 方法，这意味着用户界面可能不会更新以展示新的状态。
通过以这种方式管理状态，你不需要编写用于创建和更新子 widget 的单独代码。相反，你只需实现 build 函数，它可以处理这两种情况。



响应 widget 的生命周期事件
在 StatefulWidget 上调用 createState() 之后，框架将新的状态对象插入到树中，然后在状态对象上调用 initState()。 State 的子类可以重写 initState 来完成只需要发生一次的工作。例如，重写 initState 来配置动画或订阅平台服务。实现 initState 需要调用父类的 super.initState 方法来开始。

当不再需要状态对象时，框架会调用状态对象上的 dispose() 方法。可以重写dispose 方法来清理状态。例如，重写 dispose 以取消计时器或取消订阅平台服务。实现 dispose 时通常通过调用 super.dispose 来结束。

有关更多信息，请参阅 State。



Keys
使用 key 可以控制框架在 widget 重建时与哪些其他 widget 进行匹配。默认情况下，框架根据它们的 runtimeType 以及它们的显示顺序来匹配。使用 key 时，框架要求两个 widget 具有相同的 key 和 runtimeType。

Key 在构建相同类型 widget 的多个实例时很有用。例如，ShoppingList widget，它只构建刚刚好足够的 ShoppingListItem 实例来填充其可见区域：

如果没有 key，当前构建中的第一个条目将始终与前一个构建中的第一个条目同步，在语义上，列表中的第一个条目如果滚动出屏幕，那么它应该不会再在窗口中可见。

通过给列表中的每个条目分配为“语义” key，无限列表可以更高效，因为框架将通过相匹配的语义 key 来同步条目，并因此具有相似（或相同）的可视外观。此外，语义上同步条目意味着在有状态子 widget 中，保留的状态将附加到相同的语义条目上，而不是附加到相同数字位置上的条目。

有关更多信息，请参阅 Key API。




全局 key
全局 key 可以用来标识唯一子 widget。全局 key 在整个 widget 结构中必须是全局唯一的，而不像本地 key 只需要在兄弟 widget 中唯一。由于它们是全局唯一的，因此可以使用全局 key 来检索与 widget 关联的状态。

有关更多信息，请参阅 GlobalKey API。



 */
